package com.fudan.framework.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import com.fudan.framework.dao.BaseRepository;

/**
 * JPA baseDao的实现，重写部分逻辑
 * 如果实体中存在discard属性则将删除改为逻辑删除
 * @author Xuxinxing
 *
 * @param <T>
 * @param <ID>
 */
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID>{
	protected Logger logger = LoggerFactory.getLogger(getClass());
	
	private final EntityManager em;
	public BaseRepositoryImpl(JpaEntityInformation<T, ID> infomation,EntityManager em) {
		super(infomation,em);
		this.em=em;
	}
	/**
	 * 分页获取
	 */
	@Override
	public List<T> findAllWithPage(int page, int size) {
		Specification<T> specification = new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
				Class<? extends T> actualClass = root.getJavaType();
				try {
					if(doesClassHasField(actualClass, "discard")) {
						Predicate condition = criteriaBuilder.equal(root.get("discard"), 0);
						query.where(condition);
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				return null;
			}
		};
		return findAll(specification, PageRequest.of(page, size)).getContent();
	}

	/**
	 * 批量删除
	 */
	@Override
	public void deleteAll(Iterable<? extends T> entities) {

		Iterator<? extends T> iterator = entities.iterator();
		while(iterator.hasNext()) {
			T entity = iterator.next();
			try {
				Class<? extends Object> entityClass = entity.getClass();
				if(doesClassHasField(entityClass,"discard")) {
					Field discard = entityClass.getDeclaredField("discard");
					discard.setAccessible(true);
					Method method = entityClass.getDeclaredMethod("setDiscard", discard.getType());
					method.invoke(entity, true);
					saveAndFlush(entity);
				}else {
					super.delete(entity);
				}
			}catch(Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	/**
	 * 根据id删除
	 */
	@Override
	public void deleteById(ID id) {
		T t = getById(id);
		delete(t);
	}

	/**
	 * 全部记录个数
	 */
	@Override
	public long count() {
		Specification<T> specification = new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
				Class<? extends T> actualClass = root.getJavaType();
				try {
					if(doesClassHasField(actualClass, "discard")) {
						Predicate condition = criteriaBuilder.equal(root.get("discard"), 0);
						query.where(condition);
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				return null;
			}
		};
		return super.count(specification);
	}

	/**
	 * 条件分页
	 */
	@Override
	public List<T> findAllWithPageAndCondition(int page, int size, T condition) {
		Example<T> example = Example.of(condition,ExampleMatcher.matching().withIgnoreNullValues().withIgnorePaths("id"));
		return findAll(example, PageRequest.of(page, size)).getContent();
	}
	public List<T> findAllWithCondition(T condition) {
		Example<T> example = Example.of(condition,ExampleMatcher.matching().withIgnoreNullValues().withIgnorePaths("id"));
		return findAll(example);
	}
	@Override
	public T findOneWithCondition(T condition) {
		Example<T> example = Example.of(condition,ExampleMatcher.matching().withIgnoreNullValues().withIgnorePaths("id"));
		return findOne(example).orElse(null);
	}
	@Override
	public long countWithCondition(T condition) {
		Example<T> example = Example.of(condition,ExampleMatcher.matching().withIgnoreNullValues().withIgnorePaths("id"));
		return count(example);
	}
	/**
	 * 缩写查询
	 */
	@Override
	public List<T> findAllWithTermAndAbbr(String fieldName,String value,int page,int size) {
		Specification<T> specification = new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
				Class<? extends T> actualClass = root.getJavaType();
				try {
					Predicate condition = null;
					if(doesClassHasField(actualClass,fieldName)) {
						condition = criteriaBuilder.like(root.get(fieldName),"%"+value+"%");
					}
					String fieldAbbr = fieldName+"Abbr";

					if(doesClassHasField(actualClass,fieldAbbr)) {
						Predicate abbrLike = criteriaBuilder.like(root.get(fieldAbbr), "%"+value+"%");
						if(condition!=null) {
							condition = criteriaBuilder.or(condition,abbrLike);
						}else {
							condition = abbrLike;
						}
					}
					if(actualClass.getDeclaredField("discard") != null) {
						Predicate discard = criteriaBuilder.equal(root.get("discard"), 0);
						if(condition!=null) {
							condition = criteriaBuilder.and(condition,discard);
						}else {
							condition = discard;
						}
					}

					if(condition != null) {
						query.where(condition);
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				return null;
			}
		};
		return findAll(specification, PageRequest.of(page, size)).getContent();
	}

	/**
	 * 保存
	 */
	@Override
	public <S extends T> S save(S entity) {
		try {
			Class<? extends Object> entityClass = entity.getClass();
			if(doesClassHasField(entityClass,"discard")) {
				Field discard = entityClass.getDeclaredField("discard");
				discard.setAccessible(true);
				if(discard.get(entity) == null) {
					Method method = entityClass.getDeclaredMethod("setDiscard", discard.getType());
					method.invoke(entity, false);
				}
			}
			return super.save(entity);
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 保存
	 */
	@Override
	public <S extends T> S saveAndFlush(S entity) {
		try {
			Class<? extends Object> entityClass = entity.getClass();
			if(doesClassHasField(entityClass,"discard")) {
				Field discard = entityClass.getDeclaredField("discard");
				discard.setAccessible(true);
				if(discard.get(entity) == null) {
					Method method = entityClass.getDeclaredMethod("setDiscard", discard.getType());
					method.invoke(entity, false);
				}
			}
			return super.saveAndFlush(entity);
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 删除
	 */
	@Override
	public void delete(T entity) {
		try {
			Class<? extends Object> entityClass = entity.getClass();
			if(doesClassHasField(entityClass,"discard")) {
				Field discard = entityClass.getDeclaredField("discard");
				discard.setAccessible(true);
				Method method = entityClass.getDeclaredMethod("setDiscard", discard.getType());
				method.invoke(entity, true);
				saveAndFlush(entity);
			}else {
				super.delete(entity);
			}
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 根据id获取
	 */
	@Override
	public T getById(Object id) {
		Specification<T> specification = new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
				Class<? extends T> actualClass = root.getJavaType();
				try {
					Predicate predicted = null;
					if(doesClassHasField(actualClass, "discard")) {
						Predicate condition = criteriaBuilder.equal(root.get("discard"), 0);
						predicted = criteriaBuilder.and(condition);
					}
					if(doesClassHasField(actualClass,"id")) {
						Predicate equal = criteriaBuilder.equal(root.get("id"),id);
						if(predicted != null) {
							predicted = criteriaBuilder.and(predicted,equal);
						}else {
							predicted = criteriaBuilder.and(equal);
						}
					}
					if(predicted == null) {
						throw new RuntimeException("there is no identity of entity");
					}
					query.where(predicted);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				return null;
			}
		};
		return findOne(specification).orElse(null);
	}
	/**
	 * 判断Class中是否存在字段
	 * @param clz 
	 * @param fieldName
	 * @return
	 */
	private boolean doesClassHasField(Class<?> clz, String fieldName) {
	    return Arrays.stream(clz.getDeclaredFields())
	            .anyMatch(f -> f.getName().equals(fieldName));
	}
}
